local _G = _G

local math_ceil = _G.math.ceil
local math_floor = _G.math.floor
local math_fmod = _G.math.fmod
local CreateFrame = _G.CreateFrame
local GetTime = _G.GetTime

local AceLibrary = _G.AceLibrary

local CooldownCount = AceLibrary("AceAddon-2.0"):new("AceDB-2.0", "AceHook-2.1", "AceConsole-2.0")
local L = AceLibrary("AceLocale-2.2"):new("CooldownCount")
local SM = AceLibrary("SharedMedia-1.0")
local SM_font

local defaults = {
	shine = false,
	shineScale = 2,
	minimumDuration = 3,
	hideAnimation = false,
	font = "Friz Quadrata TT",
	color1 = {r=1, g=1, b=0.2},
	color2 = {r=1, g=0, b=0},
	size = {[1] = 18, [2] = 24, [3] = 28, [4] = 34}
}

local commands = { "/cooldowncount", "/cc" }

local options = {
	type = "group",
	args = {
		reset = {
			type = "execute",
			name = L["reset"],
			desc = L["Reset all settings."],
			func = function()
				CooldownCount:ResetDB("profile")
				CooldownCount:Print(L["Reset to default settings."])
			end
		},
		shine = {
			type = "toggle",
			name = L["shine"],
			desc = L["Toggle icon shine display after finish cooldown."],
			get = function() return CooldownCount.db.profile.shine end,
			set = function(t) CooldownCount.db.profile.shine = t end
		},
		shinescale = {
			type = "range",
			name = L["shinescale"],
			desc = L["Adjust icon shine scale."],
			min = 0,
			max = 100,
			step = 0.1,
			get = function() return CooldownCount.db.profile.shineScale end,
			set = function(r) CooldownCount.db.profile.shineScale = r end
		},
		minimum_duration = {
			type = "range",
			name = L["minimum duration"],
			desc = L["Minimum duration for display cooldown count."],
			min = 0.5,
			max = 100,
			step = 0.5,
			get = function() return CooldownCount.db.profile.minimumDuration end,
			set = function(r) CooldownCount.db.profile.minimumDuration = r end
		},
		hide_animation = {
			type = "toggle",
			name = L["hide animation"],
			desc = L["Hide Bliz origin cooldown animation."],
			get = function() return CooldownCount.db.profile.hideAnimation end,
			set = function(t) CooldownCount.db.profile.hideAnimation = t end
		},
		font = {
			type = "text",
			name = L["font"],
			desc = L["Set cooldown value display font."],
			get = function() return CooldownCount.db.profile.font end,
			set = function(f)
				CooldownCount.db.profile.font = f
				SM_font = SM:Fetch("font", f)
			end,
			validate = SM:List('font'),
		},
		color1 = {
			type = "color",
			name = L["common color"],
			desc = L["Setup the common color for value display."],
			get = function() return CooldownCount.db.profile.color1.r, CooldownCount.db.profile.color1.g, CooldownCount.db.profile.color1.b end,
			set = function(r, g, b, a) CooldownCount.db.profile.color1.r, CooldownCount.db.profile.color1.g, CooldownCount.db.profile.color1.b = r, g, b end,
		},
		color2 = {
			type = "color",
			name = L["warning color"],
			desc = L["Setup the warning color for value display."],
			get = function() return CooldownCount.db.profile.color2.r, CooldownCount.db.profile.color2.g, CooldownCount.db.profile.color2.b end,
			set = function(r, g, b, a) CooldownCount.db.profile.color2.r, CooldownCount.db.profile.color2.g, CooldownCount.db.profile.color2.b = r, g, b end,
		},
		size = {
			type = "group",
			name = L["font size"],
			desc = L["Setup cooldown value display font size."],
			args = {
				small = {
					type = "range",
					name = L["small size"],
					desc = L["Small font size for cooldown is longer than 10 minutes."],
					min = 10,
					max = 45,
					step = 1,
					get = function() return CooldownCount.db.profile.size[1] end,
					set = function(r) CooldownCount.db.profile.size[1] = r end
				},
				medium = {
					type = "range",
					name = L["medium size"],
					desc = L["Medium font size for cooldown is longer than 1 minute and less than 10 minutes."],
					min = 10,
					max = 45,
					step = 1,
					get = function() return CooldownCount.db.profile.size[2] end,
					set = function(r) CooldownCount.db.profile.size[2] = r end
				},
				large = {
					type = "range",
					name = L["large size"],
					desc = L["Large font size for cooldown is longer than 10 seconds and less than 1 minutes."],
					min = 10,
					max = 45,
					step = 1,
					get = function() return CooldownCount.db.profile.size[3] end,
					set = function(r) CooldownCount.db.profile.size[3] = r end
				},
				warn = {
					type = "range",
					name = L["warning size"],
					desc = L["Warning font size for cooldown is less than 10 seconds."],
					min = 10,
					max = 45,
					step = 1,
					get = function() return CooldownCount.db.profile.size[4] end,
					set = function(r) CooldownCount.db.profile.size[4] = r end
				}
			}
		}
	}
}

function CooldownCount:OnInitialize()
	self:RegisterDB("CooldownCountDB")
	self:RegisterDefaults("profile", defaults)
	self:RegisterChatCommand(commands, options)
	SM_font = SM:Fetch("font", self.db.profile.font)
end

function CooldownCount:OnEnable()
	self:SecureHook("CooldownFrame_SetTimer")
end

function CooldownCount:CooldownFrame_SetTimer(frame, start, duration, enable)
	local p = self.db.profile
	if p.hideAnimation then
		frame:Hide()
	end

	local textFrame = frame.textFrame
	if start > 0 and duration > p.minimumDuration and enable > 0 then
		if not textFrame then
			textFrame = self:CreateCooldownCount(frame, start, duration)
		end
		textFrame.start = start
		textFrame.duration = duration
		textFrame.timeToNextUpdate = 0
		textFrame:Show()
	elseif textFrame then
		textFrame:Hide()
	end
end

local function Text_OnUpdate (this, elapsed)
	local icon = this.icon
	if this.timeToNextUpdate <= 0 or not icon:IsVisible() then
		local p = CooldownCount.db.profile
		local remain = this.duration - (GetTime() - this.start)

		if math_floor(remain + 0.5) > 0 and icon:IsVisible() then
			local text, toNextUpdate, size, twoColor = CooldownCount:GetFormattedTime(remain)
			local fs = this.text
			fs:SetFont(SM_font, size, "OUTLINE")

			local color = p.color1
			if twoColor then
				if this.twoColor then
					color = p.color2
					this.twoColor = nil
				else
					this.twoColor = true
				end
			end
			fs:SetTextColor(color.r, color.g, color.b)

			fs:SetText(text)
			this.timeToNextUpdate = toNextUpdate
		else
			if p.shine and icon:IsVisible() then
				CooldownCount:StartToShine(this)
			end
			this.twoColor = nil
			this:Hide()
		end
	else
		this.timeToNextUpdate = this.timeToNextUpdate - elapsed
	end
end

function CooldownCount:CreateCooldownCount(frame, start, duration)
	local parent = frame:GetParent()
	local textFrame = CreateFrame("Frame", nil, parent)
	frame.textFrame = textFrame

	textFrame:SetAllPoints(parent)
	textFrame:SetFrameLevel(textFrame:GetFrameLevel() + 1)

	local text = textFrame:CreateFontString(nil, "OVERLAY")
	textFrame.text = text
	text:SetPoint("CENTER", textFrame, "CENTER", 0, -1)

	local pname = parent:GetName()
	local icon =
		--standard action button icon, $parentIcon
		_G[pname.."Icon"] or
		--standard item button icon,  $parentIconTexture
		_G[pname.."IconTexture"] or
		--discord action button, $parent_Icon
		_G[pname.."_Icon"]

	if icon then
		textFrame.icon = icon
		textFrame:SetScript("OnUpdate", Text_OnUpdate)
	end

	textFrame:Hide()

	return textFrame
end

function CooldownCount:GetFormattedTime(secs)
	-- return cc now value, next update value, font size, toggle two color
	local s = self.db.profile.size
	
	if secs >= 86400 then
		return math_ceil(secs / 86400) .. L["d"], math_fmod(secs, 86400), s[1]
	elseif secs >= 3600 then
		return math_ceil(secs / 3600) .. L["h"], math_fmod(secs, 3600), s[1]
	elseif secs >= 600 then
		return math_ceil(secs / 60) .. L["m"], math_fmod(secs, 60), s[1]
	elseif secs >= 60 then
		return math_ceil(secs / 60) .. L["m"], math_fmod(secs, 60), s[2]
	elseif secs >= 10 then
		return math_floor(secs + 0.5), secs - math_floor(secs), s[3]
	end
	return math_floor(secs + 0.5), 0.5, s[4], true
end


--[[ Shine Codes ]]--
function CooldownCount:StartToShine(textFrame)
	local scale = self.db.profile.shineScale
	local shineFrame = textFrame.shine
	if not shineFrame then
		shineFrame = self:CreateShineFrame(textFrame:GetParent())
		textFrame.shine = shineFrame
	end

	local shine = shineFrame.shine
	
	shine:SetHeight(shineFrame:GetHeight() * scale)
	shine:SetWidth(shineFrame:GetWidth() * scale)

	shineFrame:Show()
end

local function Shine_Update(this)
	local scale = CooldownCount.db.profile.shineScale
	local shine = this.shine
	local alpha = shine:GetAlpha()
	shine:SetAlpha(alpha * 0.95)

	if alpha < 0.1 then
		this:Hide()
	else
		shine:SetHeight(alpha * this:GetHeight() * scale)
		shine:SetWidth(alpha * this:GetWidth() * scale)
	end
end

function CooldownCount:CreateShineFrame(parent)
	local shineFrame = CreateFrame("Frame", nil, parent)
	shineFrame:SetAllPoints(parent)

	local shine = shineFrame:CreateTexture(nil, "OVERLAY")
	shine:SetTexture("Interface\\Cooldown\\star4")
	shine:SetPoint("CENTER", shineFrame, "CENTER")
	shine:SetBlendMode("ADD")
	shineFrame.shine = shine

	shineFrame:Hide()
	shineFrame:SetScript("OnUpdate", Shine_Update)

	return shineFrame
end

_G.CooldownCount = CooldownCount